import { Component, cls, type StyledProps } from '@knno/jsx';
import SVGSetting from '@mdi/svg/svg/cog.svg';
import SVGDrag from '@mdi/svg/svg/drag.svg';
import SVGMenuDown from '@mdi/svg/svg/menu-down.svg';
import SVGAdd from '@mdi/svg/svg/plus-box-outline.svg';
import SVGDelete from '@mdi/svg/svg/trash-can-outline.svg';
import { DICT } from '../../dict/dict';
import { vars } from '../../theme/vars.css';
import { defineElement } from '../defineElement';
import { Dialog } from '../dialog/dialog';
import { Icon } from '../icon/icon';
import { PopMenu } from '../popMenu/popMenu';
import { DATA } from '../symbol';
import { Tab } from '../tab/tab';
import { controlBox, controlMenu, formStyle } from './form.css';
import {
	Capability,
	type FormControl,
	type FormControlDefinition,
	type FormMode,
} from './formControl';

type FormControlConstructorParams = ConstructorParameters<typeof FormControl>;

type FormControlConstructor = (new (
	...args: FormControlConstructorParams
) => FormControl<FormControlDefinition>) & {
	readonly type: string;
	readonly name: string;
	readonly icon: string;
};

const BUILT_IN: { [key: string]: FormControlConstructor } = {};

export type FormProps = StyledProps<Form> & {
	controls?: FormControlDefinition[];
	plugins?: FormControlConstructor[];
	data?: Record<string, unknown>;
	mode?: FormMode;
	readonly?: boolean;
};

const FormElement = defineElement('knno-form', HTMLElement);

type Control = {
	constructor: FormControlConstructor;
	instance: FormControl<FormControlDefinition>;
};

export class Form extends Component<FormProps> {
	private [DATA]: {
		controls: Control[];
		plugins: { [key: string]: FormControlConstructor };
	} = {
		controls: [],
		plugins: {},
	};
	constructor(props: FormProps) {
		super(props);
		this.setPlugins(this.props.plugins ?? []);
		this.setControls(this.props.controls ?? []);
		this.setData(this.data);
	}
	protected render(): Node {
		return <FormElement class={cls(this.props.class, formStyle)} />;
	}

	private createMenu(control: FormControl<FormControlDefinition>) {
		const insertMenu = Object.entries(this[DATA].plugins)
			.concat(Object.entries(BUILT_IN))
			.map((item) => {
				return {
					icon: item[1].icon,
					key: item[0],
					text: item[1].name,
				};
			});
		const menu = control.menu ?? [];
		if (menu.length > 0) {
			menu.push({
				type: 'line',
			});
		}
		menu.push(
			{
				icon: SVGAdd,
				key: 'insertAbove',
				text: DICT.insertAbove,
				children: insertMenu,
			},
			{
				icon: SVGAdd,
				key: 'insertBelow',
				text: DICT.insertBelow,
				children: insertMenu,
			},
			{
				type: 'line',
			},
			{
				icon: SVGDelete,
				key: 'remove',
				text: DICT.delete,
			},
		);
		return menu;
	}

	private showSetting(control: Control) {
		Dialog.create(
			<div style={{ textAlign: 'left' }}>
				<Tab
					appearance="simple"
					tabPanelColor={vars.color.panelBackground}
					tabs={[
						{
							text: DICT.normal,
						},
					]}
				>
					<div>haha</div>
				</Tab>
			</div>,
		)
			.setPadding('1em')
			.setAutoClose(true)
			.setButtons()
			.show({});
	}

	private renderContent() {
		this.el.replaceChildren(
			...this[DATA].controls.map((c, idx) => {
				const control = c.instance.render({
					mode: this.mode,
					readonly: this.isControlReadonly(c.instance, []),
					disabled: this.isControlDisabled(c.instance, []),
					value: this.getControlValue(c.instance, []),
				});
				if (this.mode === 'design') {
					let box: HTMLDivElement;
					let menuBar: HTMLDivElement;
					const showMenu = () => {
						const menu = this.createMenu(c.instance);
						box.classList.add('popup-menu');
						PopMenu.show(menu, {
							refer: menuBar,
						}).onWillClose(() => {
							box.classList.remove('popup-menu');
						});
					};
					return (
						<div class={controlBox} tabIndex={0} ref={(b) => (box = b)}>
							<div class={controlMenu} ref={(m) => (menuBar = m)}>
								<Icon svg={SVGDrag} class="drag" />
								<Icon svg={c.constructor.icon} onClick={showMenu} />
								<Icon svg={SVGMenuDown} onClick={showMenu} style={{ marginLeft: '-0.2em' }} />
								<Icon
									svg={SVGSetting}
									size="0.9em"
									onClick={() => {
										this.showSetting(c);
									}}
								/>
							</div>
							{control}
						</div>
					);
				} else {
					return control;
				}
			}),
		);
	}

	get controls(): FormControlDefinition[] {
		if (this[DATA].controls === undefined) {
			this[DATA].controls = [];
		}
		return this[DATA].controls.map((c) => {
			return c.instance.definition;
		});
	}

	private checkControlState(
		c: FormControl<FormControlDefinition>,
		dependencies: string[],
		capability: Capability,
		propName: 'disabled' | 'readonly',
	) {
		if (c.capability & capability) {
			if (typeof c.definition[propName] === 'boolean') {
				return c.definition[propName];
			} else if (typeof c.definition[propName] === 'function') {
				return c.definition[propName](c.value, {
					keys: this.getControlKeys(),
					getValue: (key: string) => {
						const target = this[DATA].controls.find((c) => c.instance.definition.key === key);
						if (target) {
							return this.getControlValue(target.instance, dependencies.concat(key));
						} else {
							return undefined;
						}
					},
				});
			} else {
				return false;
			}
		} else {
			return false;
		}
	}

	private isControlDisabled(c: FormControl<FormControlDefinition>, dependencies: string[]) {
		return this.checkControlState(c, dependencies, Capability.Disabled, 'disabled');
	}
	private isControlReadonly(c: FormControl<FormControlDefinition>, dependencies: string[]) {
		return this.checkControlState(c, dependencies, Capability.Readonly, 'readonly');
	}

	private getControlValue(c: FormControl<FormControlDefinition>, dependencies: string[]) {
		if (!this.isControlDisabled(c, dependencies)) {
			if (c.capability & Capability.Formula) {
				if (typeof c.definition.formula === 'function') {
					return c.definition.formula(c.value, {
						keys: this.getControlKeys(),
						getValue: (key): unknown => {
							const target = this[DATA].controls.find((c) => c.instance.definition.key === key);
							if (target) {
								return this.getControlValue(target.instance, dependencies.concat(key));
							} else {
								return undefined;
							}
						},
					});
				} else {
					return c.value;
				}
			} else {
				return c.value;
			}
		} else {
			return undefined;
		}
	}

	private getControlKeys(): string[] {
		return this[DATA].controls
			.filter((c) => !!c.instance.definition.key)
			.map((c) => c.instance.definition.key!);
	}

	setControls(controls: FormControlDefinition[]): this {
		this[DATA].controls = controls
			.map((c) => {
				const ctor = this[DATA].plugins[c.type] ?? BUILT_IN[c.type];
				if (ctor) {
					return { constructor: ctor, instance: new ctor(this, c) };
				} else {
					return undefined;
				}
			})
			.filter((c) => c !== undefined);
		this.renderContent();
		return this;
	}

	getValue(key: string): unknown {
		const control = this[DATA].controls.find((c) => c.instance.definition.key === key);
		if (control) {
			return this.getControlValue(control.instance, []);
		} else {
			return undefined;
		}
	}

	get data(): Record<string, unknown> {
		return this[DATA].controls
			.filter((c) => !!c.instance.definition.key)
			.map((c) => ({
				key: c.instance.definition.key!,
				value: this.getControlValue(c.instance, []),
			}))
			.reduce(
				(p, v) => {
					p[v.key] = v.value;
					return p;
				},
				{} as Record<string, unknown>,
			);
	}
	setData(data: Record<string, unknown>): this {
		this[DATA].controls.forEach((c) => {
			if (c.instance.definition.key) {
				c.instance.setValue(data[c.instance.definition.key]);
			}
		});
		this.renderContent();
		return this;
	}

	get mode(): FormMode {
		return this.props.mode ?? 'edit';
	}
	setMode(value: FormMode): this {
		this.props.mode = value;
		this.renderContent();
		return this;
	}

	private setPlugins(plugins: FormControlConstructor[]): this {
		plugins.forEach((c) => {
			this[DATA].plugins[c.type] = c;
		});
		return this;
	}
}
